﻿#include "Ppu.h"

// Примечание:
// Символ - блок изображения размером 8 x 8, для формирования фона
// Спрайт - блок изображения размером 8 x 8, или 8 x 16, который обрабатывается независимо от основного изображения ( фона )


void Ppu_t::SavePpuContext( uint8_t* pOutData )
{
    uint8_t* pData = pOutData;
    uint8_t* pSource;


    *pData += m_PpuRegisters.AS;
    *pData += m_PpuRegisters.AV.lowPart;
    *pData += m_PpuRegisters.AV.highPart;
    *pData += m_PpuRegisters.DS;
    *pData += m_PpuRegisters.DV;
    *pData += m_PpuRegisters.HS.HorizontalValue;
    *pData += m_PpuRegisters.HS.VerticalValue;
    *pData += m_PpuRegisters.C1.value;
    *pData += m_PpuRegisters.C2.value;
    *pData += m_PpuRegisters.SR.value;

    for( uint8_t i = 0; i != PPU_FONE_PALETTE_SIZE; i++ )
    {
        *pData ++= m_PaletteFone[ i ]; 
    }

    for( uint8_t i = 0; i != PPU_SPRITES_PALETTE_SIZE; i++ )
    {
        *pData ++= m_PaletteSprites[ i ]; 
    }

    pSource = reinterpret_cast< uint8_t* >( &m_SpritesMemory );

    for( uint16_t i = 0; i != PPU_SPRITES_MEMORY_SIZE; i++ )
    {
        *pData ++= *pSource++; 
    }  
}

void Ppu_t::LoadPpuContext( uint8_t* pInData )
{
    uint8_t* pData = pInData;
    uint8_t* pSource;

    m_PpuRegisters.AS                 = *pData++;
    m_PpuRegisters.AV.lowPart         = *pData++;
    m_PpuRegisters.AV.highPart        = *pData++;
    m_PpuRegisters.DS                 = *pData++;
    m_PpuRegisters.DV                 = *pData++;
    m_PpuRegisters.HS.HorizontalValue = *pData++;
    m_PpuRegisters.HS.VerticalValue   = *pData++;
    m_PpuRegisters.C1.value           = *pData++;
    m_PpuRegisters.C2.value           = *pData++;
    m_PpuRegisters.SR.value           = *pData++;

    for( uint8_t i = 0; i != PPU_FONE_PALETTE_SIZE; i++ )
    {
        m_PaletteFone[ i ] = *pData ++; 
    }

    for( uint8_t i = 0; i != PPU_SPRITES_PALETTE_SIZE; i++ )
    {
        m_PaletteSprites[ i ] = *pData ++; 
    }

    pSource = reinterpret_cast< uint8_t* >( &m_SpritesMemory );

    for( uint16_t i = 0; i != PPU_SPRITES_MEMORY_SIZE; i++ )
    {
        *pSource++ = *pData ++; 
    }  
}
      


//void Ppu_t::SetContextPointer( _in_ void * context )
//{
//    m_Context = context;
//}

void Ppu_t::SetBusWriteCallBack( PpuBusWriteCallBack_t busWriteCallBack, void * pContext )
{
    fp_PpuBusWriteCallBack = busWriteCallBack;
    m_pContext = pContext;
}

void Ppu_t::SetBusReadCallBack( PpuBusReadCallBack_t busReadCallBack, void * pContext )
{
    fp_PpuBusReadCallBack = busReadCallBack;
    m_pContext = pContext;
}
    
void Ppu_t::SetPresentFrameCallBack( PpuPresentFrameCallBack_t presentFrameCallBack, void * pContext )
{
    fp_PresentFrameCallBack = presentFrameCallBack;
    m_pContext = pContext;
}

void Ppu_t::SetVsyncSignalCallBack( PpuInterruptCallBack_t vsyncCallBack, void * pContext )
{
    fp_VsyncSignalCallBack = vsyncCallBack;
    m_pContext = pContext;
}

void Ppu_t::SetCpuExecuteCallBack( CpuExecuteCallBack_t cpuExecuteCallBack, void * pContext )
{
    fp_CpuExecuteCallBack = cpuExecuteCallBack;
    m_pContext = pContext;
}

void Ppu_t::SetLineCounterCallBack( PpuInterruptCallBack_t lineCounterCallBack, void * pContext )
{
    fp_IrqHookCallBack = lineCounterCallBack;
    m_pContext = pContext;
}

void Ppu_t::SetVideoSystem( VideoEncodingSystem_t videoSystem )
{

}


bool Ppu_t::IsNmiBlank( void )
{
    return (bool)m_PpuRegisters.C1.NmiRequestEnable;
}

//void Ppu_t::PostFrame( void )
//{
//    //m_PpuRegisters.SR.VsyncFlag = 1;
// //   m_PpuRegisters.SR.VideoRamWriteDisable = 0; //
//    //m_PpuRegisters.SR.ZeroSpriteDetected = 0;
//}

void Ppu_t::Reset(void)
{
    //m_PpuRegisters.Reset();
   
    
    m_PpuRegisters.AS=0;
    m_PpuRegisters.DS=0;
    m_PpuRegisters.HS.HorizontalValue = 0;
    m_PpuRegisters.HS.VerticalValue   = 0;
    m_PpuRegisters.AV.value=0;
    m_PpuRegisters.DV=0;
    m_VideoRamAddrRegHighPart = true;
    m_VerticalScrollingValue  = false;
    
    m_SpriteSize = 8;
   // m_NewAddrWasSet = false;
    m_VideoRamAddrRegHighPart = true;
}

//void Ppu_t::Sync( void )
//{
//    //m_PpuRegisters.SR.ZeroSpriteDetected = 0;
// //   m_PpuRegisters.SR.SpriteCountPerLine = 0;
// //   m_PpuRegisters.SR.VsyncFlag          = 0;
//}

//void Ppu_t::SetCyclesPerFrame(uint16_t cycles)
//{
//    //m_CyclesPerFrame = cycles;
//}

void Ppu_t::ReflectPalette( void )
{
    switch( m_PpuRegisters.AV.value & 0x000F )
    {
        case 0:
            m_PaletteSprites[ 0 ] = m_PpuRegisters.DV;
            m_PaletteFone   [ 0 ] = m_PpuRegisters.DV;
        break;
                
        case 4:
            m_PaletteSprites[ 4 ] = m_PpuRegisters.DV;
            m_PaletteFone   [ 4 ] = m_PpuRegisters.DV;
        break;

        case 8:
            m_PaletteSprites[ 8 ] = m_PpuRegisters.DV;
            m_PaletteFone   [ 8 ] = m_PpuRegisters.DV;
        break;

        case 12:
            m_PaletteSprites[ 12] = m_PpuRegisters.DV;
            m_PaletteFone   [ 12] = m_PpuRegisters.DV;
        break;
                
        default: break;
    }
}


uint16_t m_Addr = 0;


uint8_t Ppu_t::Read(uint16_t addr)
{
     uint8_t* pTemp; 

     switch (addr) 
     {
       case PPU_REG_STATUS_PPU_ADDR:
            m_PpuRegisters.SR.NotUsedBits = m_LastWritten;
            m_PpuRegisters.DV = *( uint8_t *)&m_PpuRegisters.SR;
            m_PpuRegisters.SR.VsyncFlag = 0;
            m_VideoRamAddrRegHighPart = true;
            m_VerticalScrollingValue = false;

            if( m_PpuRegisters.SR.ZeroSpriteDetected )
            {
                m_PpuRegisters.SR.ZeroSpriteDetected = SET_BIT;
            }

       break;    
       
       case PPU_REG_DATA_SPRITES_ADDR:
            pTemp = (uint8_t *)&m_SpritesMemory;
            m_PpuRegisters.DV = pTemp[ m_PpuRegisters.AS ];
       break;

    /*   case PPU_REG_ADDR_VRAM_ADDR:  
       
       break;*/

       case PPU_REG_DATA_VRAM_ADDR:

            if( m_PpuRegisters.AV.value < PPU_PALETTE_FONE_BASE_ADDR )
            {

            //    m_Addr &= 0x00FF;
            //    m_Addr |= m_PpuRegisters.AV.value & 0x3F00;
        
                
                
                //m_Addr |= lastAddr;


                if( m_Addr >=0x3FFF)
                {
                    m_Addr = m_Addr;
                }
            
                m_PpuRegisters.DV = fp_PpuBusReadCallBack( m_pContext , m_Addr );


                m_Addr = m_PpuRegisters.AV.value;

            }
            else if( m_PpuRegisters.AV.value < PPU_PALETTE_SPRITES_BASE_ADDR ) 
            {
                addr = m_PpuRegisters.AV.value - PPU_PALETTE_FONE_BASE_ADDR + m_AddrIncValue;
                m_PpuRegisters.DV = m_PaletteFone[ addr ];
            }
            else 
            {
                addr = m_PpuRegisters.AV.value - PPU_PALETTE_SPRITES_BASE_ADDR + m_AddrIncValue;
                   m_PpuRegisters.DV = m_PaletteSprites[ addr ];
            }
                        
           // if( false == m_NewAddrWasSet ) 
           // {
                m_PpuRegisters.AV.value += m_AddrIncValue;
           // }
           // m_NewAddrWasSet = false;
       break;
     
       default:
       
       break;
     }
     return m_PpuRegisters.DV;
}

uint16_t tempAddr = 0;

void Ppu_t::Write(uint16_t addr, uint8_t data)
{
    uint8_t* pTemp; 
    m_LastWritten = data;
    
    switch ( addr ) 
    {
    case PPU_REG_CONTROL_1_ADDR:
        m_PpuRegisters.C1 = *(PpuCtrlReg1_t *)&data;
         
        if ( m_PpuRegisters.C1.SpriteSize ) 
            m_SpriteSize = 16; 
        else 
            m_SpriteSize = 8;

        m_FoneChrBaseAddr = PPU_CHR1_ROM_BASE_ADDR * m_PpuRegisters.C1.FoneChrBaseAddr; 
        m_SpritesChrBaseAddr = PPU_CHR1_ROM_BASE_ADDR * m_PpuRegisters.C1.SpriteChrBaseAddr;
            
        if ( m_PpuRegisters.C1.IncVideoRamAddr ) 
            m_AddrIncValue = 32;
        else 
            m_AddrIncValue = 1;
           
        m_ActiveVideoRamAddr = PPU_SCREEN_PAGE1_BASE_ADDR + m_PpuRegisters.C1.ActiveScreenPage * PPU_SCREEN_PAGE_SIZE;

    break;

    case PPU_REG_CONTROL_2_ADDR:
        m_PpuRegisters.C2 = *(PpuCtrlReg2_t *)&data;         
    break;

    case PPU_REG_ADDR_SPRITES_ADDR:
        m_PpuRegisters.AS = data;
    break;

    case PPU_REG_DATA_SPRITES_ADDR:
        pTemp = ( uint8_t* )&m_SpritesMemory;
        pTemp[ m_PpuRegisters.AS ] = data;
        m_PpuRegisters.AS++;
    break;

    case PPU_REG_HARDWARE_SCROLL_ADDR:

     

        //m_PpuRegisters.C1.ActiveScreenPage

        if (m_VerticalScrollingValue == false) 
        {
    
        /*    m_PpuRegisters.C1.ActiveScreenPage = 0;
            m_ActiveVideoRamAddr = PPU_SCREEN_PAGE1_BASE_ADDR + m_PpuRegisters.C1.ActiveScreenPage * 0x0400;*/

            m_PpuRegisters.HS.HorizontalValue = data;
            m_VerticalScrollingValue = true;
        } 
        else 
        {
          

            m_PpuRegisters.HS.VerticalValue = data;
            m_VerticalScrollingValue = false;
        }
    break;

    case PPU_REG_ADDR_VRAM_ADDR:  

        if ( m_VideoRamAddrRegHighPart ) 
        {
            data &= 0x3F;
            tempAddr = data << 8;

            //m_PpuRegisters.AV.value = data << 8;
            m_VideoRamAddrRegHighPart = false;
        } 
        else 
        {
            tempAddr |= data;

            m_PpuRegisters.AV.value = tempAddr;
            //m_PpuRegisters.AV.value |= data;
            m_VideoRamAddrRegHighPart = true;
            //m_NewAddrWasSet = true;
        }

            
    break;

    case PPU_REG_DATA_VRAM_ADDR:
        m_PpuRegisters.DV = data;

        if( m_PpuRegisters.AV.value < PPU_PALETTE_FONE_BASE_ADDR ) 
        {    
            fp_PpuBusWriteCallBack( m_pContext ,  m_PpuRegisters.AV.value, m_PpuRegisters.DV);    
        }
        else if( m_PpuRegisters.AV.value < PPU_PALETTE_SPRITES_BASE_ADDR ) 
        {
            addr = m_PpuRegisters.AV.value - PPU_PALETTE_FONE_BASE_ADDR;
            m_PaletteFone   [ addr ] = m_PpuRegisters.DV & 0x3F;
            ReflectPalette();
        }
        else 
        {
            addr =  m_PpuRegisters.AV.value - PPU_PALETTE_SPRITES_BASE_ADDR;
            m_PaletteSprites[ addr ] = m_PpuRegisters.DV  & 0x3F;
            ReflectPalette();
        }    
        m_PpuRegisters.AV.value += m_AddrIncValue;
//        m_NewAddrWasSet = false;
    break;
    }
}

void Ppu_t::RenderScanLine( uint16_t scanLine )
{
    bool zeroSpriteDetected = false;


    m_PpuRegisters.SR.SpriteCountPerLine = 0;


  /*     uint8_t* pSpritesMemory = (uint8_t *)&m_SpritesMemory;
    for ( uint16_t i = 0; i < 256; i++ ) 
    {
       *pSpritesMemory ++= 0xFF;

    }*/


    for( uint16_t i = 0; i != PPU_HORIZONTAL_RESOLUTION; i++ )
    {
        m_FoneScanLine[i]         = 0;
        m_SpriteTopScanLine[i]    = 0;
        m_SpriteBottomScanLine[i] = 0;
        m_ScanLine[i] = 0;
    }
    
    if( m_PpuRegisters.C2.SpritesVisibleEnable ) 
    {
        RenderSpritesScanline( scanLine );

          if ( ( scanLine >= ((uint16_t)m_SpritesMemory[0].yPos ) ) && ( scanLine <= ( (uint16_t)m_SpritesMemory[0].yPos + m_SpriteSize + 1)) ) 
        {
            zeroSpriteDetected = true;
        }
        else
        {
            zeroSpriteDetected = false;
        }
    }

    if( m_PpuRegisters.C2.FoneVisibleEnable ) 
    {
        RenderFoneScanline( scanLine );
    }
         
    for( uint16_t xVisible = 0; xVisible < PPU_HORIZONTAL_RESOLUTION; xVisible++ ) 
    {
        
        if( zeroSpriteDetected )
        {
            if( ( xVisible >= m_SpritesMemory[0].xPos ) && ( xVisible <= ( m_SpritesMemory[0].xPos + 8 ) ) )
            {
                if( ( m_FoneScanLine[xVisible]>0 ) && ( m_SpriteTopScanLine[xVisible] || m_SpriteBottomScanLine[xVisible]) )
                {
                    m_PpuRegisters.SR.ZeroSpriteDetected = SET_BIT;
                }
            }
        }
                
        if(m_SpriteBottomScanLine[ xVisible ] == 0 ) 
        {
            m_ScanLine[xVisible] = m_PaletteFone[0];
        }
        else
        {
            m_ScanLine[xVisible] = m_PaletteSprites[ m_SpriteBottomScanLine[xVisible] ];
        }

        if( m_FoneScanLine[ xVisible ] ) 
        {
            m_ScanLine[xVisible] = m_PaletteFone[ m_FoneScanLine[xVisible] ];        
        }
     
        if ( m_SpriteTopScanLine[xVisible] )
        {
            m_ScanLine[xVisible] = m_PaletteSprites[ m_SpriteTopScanLine[ xVisible ] ];
        }

     }
  
     fp_PresentFrameCallBack( m_pContext, m_ScanLine, PPU_HORIZONTAL_RESOLUTION, PPU_HORIZONTAL_RESOLUTION * scanLine );   
}


void Ppu_t::RenderFrame( void )
{
    uint32_t scanLineCpuCycles;

    scanLineCpuCycles = PPU_VISIBLE_SCAN_LINE_LENGTH + PPU_NON_VISIBLE_SCAN_LINE_LENGTH; 
    scanLineCpuCycles /=3; 
    
    
    fp_CpuExecuteCallBack( m_pContext, 48 * PPU_PRE_RENDERING_SCAN_LINES * scanLineCpuCycles );
    m_PpuRegisters.SR.ZeroSpriteDetected = 0;


    
/*    m_Ppu.m_PpuRegisters.C1.ActiveScreenPage = 0;
    m_Ppu.m_ActiveVideoRamAddr = PPU_SCREEN_PAGE1_BASE_ADDR + m_Ppu.m_PpuRegisters.C1.ActiveScreenPage * 0x0400;
*/


   

    for( uint16_t scanline = 0; scanline < PPU_VISIBLE_SCAN_LINES; scanline++ ) 
    {
        RenderScanLine(scanline);
        fp_CpuExecuteCallBack( m_pContext, 48 * scanLineCpuCycles );
            
        if( m_PpuRegisters.C2.FoneVisibleEnable || m_PpuRegisters.C2.SpritesVisibleEnable )
        {
           // m_Cartridge.IrqCounterDecrement();
            if( fp_IrqHookCallBack )
            {
                fp_IrqHookCallBack( m_pContext );
            }
        }
    }
        
    m_PpuRegisters.SR.VsyncFlag = SET_BIT;
    fp_CpuExecuteCallBack( m_pContext, 48 * PPU_PAL_POST_RENDERING_SCAN_LINES * scanLineCpuCycles );
        
    if( IsNmiBlank() )
    {
        fp_VsyncSignalCallBack( m_pContext );
    }
                
    fp_CpuExecuteCallBack( m_pContext, 48 * PPU_PAL_SCAN_LINES_AFTER_NMI * scanLineCpuCycles );
    m_PpuRegisters.SR.VsyncFlag = CLR_BIT;
            
    fp_PresentFrameCallBack(0, 0, 0, 0);
}


void Ppu_t::RenderFoneScanline( uint16_t scanline ) 
{
    uint16_t        symbolCell;                   // Номер знакоместа на экране ( а так же в экранной странице (область символов) )
    uint8_t         xPixelOfCell;               // Номер пикселя по горизонтали в знакоместе ( экран ) 
    uint8_t         yPixelOfCell;               // Номер пикселя по вертикали в знакоместе ( экран ) 
    
    uint8_t         symbolIdx;                   // Номер символа в знакогенераторе фона
    uint8_t         symbolItemByteIdx;           // Номер атрибута символов в экранной странице ( область атрибутов )
    uint8_t         symbolItemMask; 

    uint16_t        baseVideoRamAddr;           // Базовый адрес активной экранной страници
    uint16_t        videoRamAddr;
    uint16_t        xScreenPage;               // Положение пикселя по горизонтали от начала экранной страници
    uint16_t        yScreenPage;               // Положение пикселя по вертикали от начала экранной страници

    uint16_t        xFourScreen;               // Положение пикселя по горизонтали для изображения из 4 экранных страниц
    uint16_t        yFourScreen;               // Положение пикселя по вертикали для изображения из 4 экранных страниц

    uint8_t         fonePixelColor;               // Содержит в себе цвет текущего пикселя фона    
    uint8_t         symbolItemValue;

    uint16_t        startx;
    
    uint8_t         symbolLineOffset;
    
    /*if ( 0 == m_PpuRegisters.C2.FoneVisibleRegion ) 
    {
        startx = 8; 
    }
    else*/
    {
        startx = 0; 
    }

    for( uint16_t xVisible = startx; xVisible < PPU_HORIZONTAL_RESOLUTION; xVisible++ ) 
    {
        xFourScreen  = m_PpuRegisters.HS.HorizontalValue + xVisible; 
        xScreenPage  = xFourScreen % PPU_HORIZONTAL_RESOLUTION;        
        xPixelOfCell = xScreenPage % PPU_ROM_SYMBOL_WIDTH;
        
        yFourScreen  = m_PpuRegisters.HS.VerticalValue + scanline; 
        
        yScreenPage  = yFourScreen % PPU_VERTICAL_PAL_RESOLUTION;
        yPixelOfCell = yScreenPage % PPU_ROM_SYMBOL_HEIGHT;

        symbolItemMask  = ( xScreenPage % 32 ) / 16 + 2 * ( ( yScreenPage % 32 ) / 16 );
        symbolItemByteIdx  = xScreenPage / 32 + (yScreenPage / 32)* 8;
        symbolCell   = xScreenPage / PPU_ROM_SYMBOL_WIDTH + (yScreenPage / PPU_ROM_SYMBOL_HEIGHT) * PPU_NUMBER_OF_SYMBOLS_IN_LINE;
        
        baseVideoRamAddr = m_ActiveVideoRamAddr;
             
        if (xFourScreen >= PPU_HORIZONTAL_RESOLUTION)
        {
            xFourScreen -= PPU_HORIZONTAL_RESOLUTION;
            baseVideoRamAddr += PPU_SCREEN_PAGE_SIZE;                
        }
                                            
        if (yFourScreen >= PPU_VERTICAL_PAL_RESOLUTION) 
        {   
            yFourScreen  -= PPU_VERTICAL_PAL_RESOLUTION;
            baseVideoRamAddr += 2 * PPU_SCREEN_PAGE_SIZE;
        }
    
        if( baseVideoRamAddr >= ( PPU_SCREEN_PAGE4_BASE_ADDR + PPU_SCREEN_PAGE_SIZE) )
        {
            baseVideoRamAddr = PPU_SCREEN_PAGE1_BASE_ADDR;
        }

        if( ( 0 == ( xPixelOfCell % 8 ) ) || ( xVisible < 8) ) // !!!
        {        
            videoRamAddr = baseVideoRamAddr + symbolCell;
            symbolIdx    = fp_PpuBusReadCallBack( m_pContext, videoRamAddr );
            videoRamAddr    = m_FoneChrBaseAddr + yPixelOfCell + PPU_ROM_SYMBOL_SIZE * symbolIdx;
            m_SymbolLineLow  =  fp_PpuBusReadCallBack( m_pContext, videoRamAddr );
            m_SymbolLineHigh =  fp_PpuBusReadCallBack( m_pContext, videoRamAddr + 8 );
            videoRamAddr = baseVideoRamAddr + PPU_SCREEN_PAGE_SYMBOLS_AREA_SIZE + symbolItemByteIdx;
            m_SymbolItemByte = fp_PpuBusReadCallBack( m_pContext, videoRamAddr );        
        }
            
        symbolLineOffset = 7 - xPixelOfCell;

        fonePixelColor =   ( m_SymbolLineLow >> symbolLineOffset ) & 0x01;
        fonePixelColor |=  ( ( m_SymbolLineHigh >> symbolLineOffset ) & 0x01) << 1;
                
        if( fonePixelColor ) // Если пиксель не прозрачный
        {
            symbolItemValue = 0x03 & ( m_SymbolItemByte >> (2 * symbolItemMask) );
            symbolItemValue <<= 2;
            fonePixelColor += symbolItemValue;
            m_FoneScanLine[ xVisible ] = fonePixelColor;
        }
    }
}


void Ppu_t::RenderSpritesScanline(uint16_t scanline) 
{
    uint8_t      presentSprite = 0;
    SpriteInfo_t spriteInfo;
    uint16_t     startx;
    uint16_t     xVisible;
    uint16_t     spriteSymbolRamAddr; // Адрес символа спрайта в знакогенераторе
    uint16_t     spritesChrBaseAddr;
    uint8_t      yPixelOfSprite;     // Номер пикселя по вертикали в спрайте
    uint8_t      xPixelOfSprite;     // Номер пикселя по горизонтали в спрайте
    
    uint8_t      spriteIdx;             // Номер символа в знакогенераторе спрайтов
    uint8_t      spritePixelColor;     // Содержит в себе цвет текущего пикселя спрайта
    uint8_t      spriteLineOffset;

    spriteIdx = 0;
   
    m_PpuRegisters.SR.SpriteCountPerLine = 0;

 

    for ( uint8_t i = 0; i < PPU_NUMBER_OF_SPRITES; i++ ) 
    {
        spriteInfo = m_SpritesMemory[i];
        if ( (scanline >= spriteInfo.yPos ) && (scanline < (spriteInfo.yPos + m_SpriteSize) ) ) // Предвыборка спрайтов, которые находятся на скан-линии
        {
            m_SpritesBuff[spriteIdx] = spriteInfo;
            
            presentSprite |= 0x01 << spriteIdx;
                
            spriteIdx ++;
                
            if ( spriteIdx == PPU_MAX_NUMBER_OF_SPRITES_ON_LINE ) 
            {
                m_PpuRegisters.SR.SpriteCountPerLine = SET_BIT;
                break;
            }
        }
        
    }
    
  /*  if (0 == m_PpuRegisters.C2.SpritesVisibleRegion) 
        startx = 8;
    else */
        startx = 0;

    spritesChrBaseAddr = m_SpritesChrBaseAddr;

    for ( int8_t i = 7; i >= 0; i-- ) 
    {
        if ( presentSprite & ( 0x01 << i ) ) 
        {
            spriteInfo = m_SpritesBuff[i];

            yPixelOfSprite = scanline - spriteInfo.yPos;
            spriteIdx = spriteInfo.chrIdx; 
          
            if ( m_SpriteSize == 16 ) 
            {
                spritesChrBaseAddr = PPU_CHR1_ROM_BASE_ADDR * ( spriteIdx % 2 );
                spriteIdx &= 0xFE;
                
                if ( spriteInfo.item.verticalMirror ) 
                    yPixelOfSprite = 15 - yPixelOfSprite;
                    
                if ( yPixelOfSprite > 7) 
                    spriteIdx++;

                yPixelOfSprite &= 7;
            } 
            else 
            {
                if ( spriteInfo.item.verticalMirror ) 
                    yPixelOfSprite = 7 - yPixelOfSprite;
            }
            
            for ( uint8_t x = 0; x != 8; x++ ) 
            {
                xPixelOfSprite = x;
                if ( spriteInfo.item.horizontalMirror ) 
                    xPixelOfSprite = 7 - x;
                    
                
                xVisible = spriteInfo.xPos + x;

                if ( ( xVisible >= startx ) && ( xVisible < PPU_HORIZONTAL_RESOLUTION ) ) 
                {
                    if( 0 == x )
                    {
                        spriteSymbolRamAddr = spritesChrBaseAddr + yPixelOfSprite + spriteIdx * PPU_ROM_SYMBOL_SIZE;
                        m_SpriteLineLow  = fp_PpuBusReadCallBack( m_pContext, spriteSymbolRamAddr );
                        m_SpriteLineHigh = fp_PpuBusReadCallBack( m_pContext, spriteSymbolRamAddr + 8 );
                    }                
                    spriteLineOffset  = 7 - xPixelOfSprite;
                    spritePixelColor  = ( m_SpriteLineLow >> spriteLineOffset ) & 0x01;
                    spritePixelColor |= ( ( m_SpriteLineHigh >> spriteLineOffset ) & 0x01) << 1;

                    if (spritePixelColor > 0) 
                    {
                        spritePixelColor |= spriteInfo.item.color << 2;
                        
                        if ( spriteInfo.item.spriteUpperFone ) 
                            m_SpriteBottomScanLine[xVisible] = spritePixelColor;
                        else 
                            m_SpriteTopScanLine[xVisible] = spritePixelColor;
                    }
                }
            }
        }
    }
}